package cn.shiva.redisunit.lock.setnx;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author shiva   2022-08-19 23:39
 */
@Slf4j
@Service
public class SetnxService {

    @Value("${applicationName}")
    private String applicationName;
    private static final String LOCK_DEDUCTION = "LOCK.DEDUCTION";

    @Autowired
    private RedisTemplate redisTemplate;

    //模拟扣费业务
    public void deduction() {
        String lockValue = applicationName + ":" + UUID.randomUUID();
        try {
            //试图获取锁，并且设置30秒的锁过期时间
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(LOCK_DEDUCTION, lockValue, 30, TimeUnit.SECONDS);
            //判断是否获得锁
            if (lock == null || !lock) {
                //没获得锁，等会重新请求
                int tryCount = 0;
                while (tryCount < 100) {
                    Thread.sleep(20);
                    //重新获取锁，拿到了就执行业务方法，并且返回
                    lock = redisTemplate.opsForValue().setIfAbsent(LOCK_DEDUCTION, lockValue, 30, TimeUnit.SECONDS);
                    if (lock != null && lock) {
                        log.info("执行业务代码......");
                        return;
                    }
                    tryCount++;
                }
                //循环了 100 次，还是没用拿到过锁，直接报错返回好了
                log.error("尝试超过获取锁 100 次，异常操作，得写个异常日志");
                return;
            }
            //获得锁，执行业务代码
            log.info("执行业务代码......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            //       注意，这个判断不是原子性的，所以在高并发情况下可能出现问题；在并发较低情况下，可以容忍的异常率场景，可以使用
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            if (lockValue.equals(redisTemplate.opsForValue().get(LOCK_DEDUCTION))) {
                //redis的锁还是当前的锁
                redisTemplate.delete(LOCK_DEDUCTION);
            }
        }
    }
}
